package com.dragon.modules.yuegou.client.wechat_mini.service.impl;

import com.dragon.modules.yuegou.client.wechat_mini.client.SubscribeMessageClient;
import com.dragon.modules.yuegou.client.wechat_mini.common.MiniBusinessException;
import com.dragon.modules.yuegou.client.wechat_mini.common.MiniCode;
import com.dragon.modules.yuegou.client.wechat_mini.common.MiniException;
import com.dragon.modules.yuegou.client.wechat_mini.model.Category;
import com.dragon.modules.yuegou.client.wechat_mini.model.Keyword;
import com.dragon.modules.yuegou.client.wechat_mini.model.PriTemplate;
import com.dragon.modules.yuegou.client.wechat_mini.model.SubscribeMessage;
import com.dragon.modules.yuegou.client.wechat_mini.model.Template;
import com.dragon.modules.yuegou.client.wechat_mini.model.TemplateTitle;
import com.dragon.modules.yuegou.client.wechat_mini.request.SubscribeMessageRequest;
import com.dragon.modules.yuegou.client.wechat_mini.request.TemplateAddRequest;
import com.dragon.modules.yuegou.client.wechat_mini.request.TemplateDeleteRequest;
import com.dragon.modules.yuegou.client.wechat_mini.request.TemplateTitleListRequest;
import com.dragon.modules.yuegou.client.wechat_mini.response.CategoryResponse;
import com.dragon.modules.yuegou.client.wechat_mini.response.KeywordsResponse;
import com.dragon.modules.yuegou.client.wechat_mini.response.TemplateIdResponse;
import com.dragon.modules.yuegou.client.wechat_mini.response.TemplateResponse;
import com.dragon.modules.yuegou.client.wechat_mini.response.TemplateTitleResponse;
import com.dragon.modules.yuegou.client.wechat_mini.security.AccessTokenService;
import com.dragon.modules.yuegou.client.wechat_mini.service.SubscribeMessageService;
import com.dragon.modules.yuegou.web.Check;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * 订阅消息服务实现
 * 
 * @author liuwu
 *
 */
public class SubscribeMessageServiceImpl implements SubscribeMessageService {

	protected static final Logger LOG = LoggerFactory.getLogger(SubscribeMessageServiceImpl.class);

	/**
	 * 定义消息客户端
	 */
	private final SubscribeMessageClient client;

	/**
	 * 凭证服务
	 */
	private final AccessTokenService tokenService;

	/**
	 * 构造一个订阅消息服务
	 * 
	 * @param client       订阅消息客户端
	 * @param tokenService 凭证服务
	 */
	public SubscribeMessageServiceImpl(SubscribeMessageClient client, AccessTokenService tokenService) {
		super();
		Check.notNull(client, "client is required");
		Check.notNull(tokenService, "tokenService is required");
		this.client = client;
		this.tokenService = tokenService;
	}

	@Nullable
	private List<TemplateAddRequest> templateAddConfig;

	@Nullable
	private List<Template> templateCache;

	@Override
	public void configTemplate(List<TemplateAddRequest> request) {
		this.templateAddConfig = request;
		this.templateCache = null;
	}

	@Override
	@Nullable
	public Template getConfiguredTemplate(Integer tid) {
		if (templateCache == null && templateAddConfig != null) {
			this.templateCache = syncTemplate(templateAddConfig);
		}
		if (this.templateCache == null) {
			return null;
		}
		for (Template template : templateCache) {
			if (tid.equals(template.getTid())) {
				return template;
			}
		}
		return null;
	}

	@Override
	public List<Template> getAllConfiguredTemplate() {
		if (templateCache == null && templateAddConfig != null) {
			this.templateCache = syncTemplate(templateAddConfig);
		}
		if (this.templateCache == null) {
			return Collections.emptyList();
		}
		return new ArrayList<>(templateCache);
	}

	@Override
	public boolean send(SubscribeMessage message) {
		Check.notNull(message.getTid(), "无法发送订阅消息，必须提供模板ID(tid)");
		Template template = getConfiguredTemplate(message.getTid());
		if (template == null) {
			if (templateAddConfig == null) {
				throw new MiniException("无法发送订阅消息，请先调用configTemplate()方法配置消息模板");
			} else {
				throw new MiniException("无法发送订阅消息，小程序未配置消息模板，tid:" + message.getTid());
			}
		}
		SubscribeMessageRequest request = template.createRequest(message);
		return sendRawMessage(request);
	}

	@Override
	public boolean sendRawMessage(SubscribeMessageRequest message) {
		try {
			tokenService.executeAutoRetry(accessToken -> client.send(accessToken, message), UnaryOperator.identity());
		} catch (MiniBusinessException e) {
			if (e.is(MiniCode.USER_REFUSED)) {
				return false;
			}
			throw e;
		}
		return true;
	}

	@Override
	public String addTemplate(TemplateAddRequest request) {
		return tokenService.executeAutoRetry(accessToken -> client.addTemplate(accessToken, request),
				TemplateIdResponse::getPriTmplId);
	}

	@Override
	public void deleteTemplate(String priTmplId) {
		TemplateDeleteRequest request = new TemplateDeleteRequest();
		request.setPriTmplId(priTmplId);
		tokenService.executeAutoRetry(accessToken -> client.deleteTemplate(accessToken, request));
	}

	@Override
	public List<Category> getCategory() {
		return tokenService.executeAutoRetry(client::getCategory, CategoryResponse::getData);
	}

	@Override
	public List<Keyword> getPubTemplateKeyWordsById(Integer tid) {
		return tokenService.executeAutoRetry(accessToken -> client.getPubTemplateKeyWordsById(accessToken, tid),
				KeywordsResponse::getData);
	}

	@Override
	public TemplateTitleResponse getPubTemplateTitleList(List<Integer> categoryIds, int start, int limit) {
		String ids = categoryIds.stream().map(Object::toString).collect(Collectors.joining(","));
		return getPubTemplateTitleList0(ids, start, limit);
	}

	private TemplateTitleResponse getPubTemplateTitleList0(String ids, int start, int limit) {
		TemplateTitleListRequest request = new TemplateTitleListRequest();
		request.setIds(ids);
		request.setStart(start);
		request.setLimit(limit);
		return tokenService.executeAutoRetry(accessToken -> client.getPubTemplateTitleList(accessToken, request),
				UnaryOperator.identity());
	}

	@Override
	public Stream<TemplateTitle> getPubTemplateTitleStream(List<Integer> categoryIds) {
		final int limit = 30;
		final String ids = categoryIds.stream().map(Object::toString).collect(Collectors.joining(","));
		final Iterator<List<TemplateTitle>> iterator = new Iterator<List<TemplateTitle>>() {

			Integer count = null;
			int cursor = 0;

			@Override
			public boolean hasNext() {
				return count == null || cursor < count.intValue();
			}

			@Override
			public List<TemplateTitle> next() {
				if (!hasNext()) {
					throw new NoSuchElementException();
				}
				TemplateTitleResponse resp = getPubTemplateTitleList0(ids, cursor, limit);
				count = resp.getCount();
				cursor += resp.getData().size();
				return resp.getData();
			}
		};
		Stream<List<TemplateTitle>> sliceStream = StreamSupport.stream(
				Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
		return sliceStream.flatMap(List::stream);
	}

	@Override
	public Stream<TemplateTitle> getMyPubTemplateTitleStream() {
		List<Category> category = getCategory();
		List<Integer> categoryIds = category.stream().map(Category::getId).collect(Collectors.toList());
		return getPubTemplateTitleStream(categoryIds);
	}

	@Override
	public List<PriTemplate> getTemplateList() {
		return tokenService.executeAutoRetry(client::getTemplateList, TemplateResponse::getData);
	}

	@Override
	public List<Template> syncTemplate(List<TemplateAddRequest> requests) {
		if(requests.isEmpty()) {
			return Collections.emptyList();
		}
		List<PriTemplate> priTemplates = getTemplateList();
		// 得到标题到个人模板的映射表，如果同一个模板标题出现了多次，则取第一个
		final Map<String, PriTemplate> priTemplateTitleMap = priTemplates.stream()
				.collect(Collectors.toMap(PriTemplate::getTitle, UnaryOperator.identity(), (a, b) -> a));
		// 得到公用模板ID到添加模板请求的映射表，如果模板ID出现了多次，则取最后一个
		Map<Integer, TemplateAddRequest> tidRequestMap = requests.stream()
				.collect(Collectors.toMap(TemplateAddRequest::getTid, UnaryOperator.identity(), (a, b) -> b));
		// 查找公用模板（来自已有个人模板和请求添加的模板的交集）
		List<TemplateTitle> templates = findMyPubTemplate(tidRequestMap.keySet(), priTemplateTitleMap.keySet());

		// 找到未添加的模板进行添加
		Set<String> newPriTemplateIds = templates.parallelStream()
				.filter(t -> !priTemplateTitleMap.containsKey(t.getTitle())).map(t -> {
					// 未添加的模板
					TemplateAddRequest request = tidRequestMap.get(t.getTid());
					return addTemplate(request);
				}).collect(Collectors.toSet());
		LOG.debug("新增了{}个模板", newPriTemplateIds.size());

		// 添加模板后的个人模板映射表
		Map<String, PriTemplate> priTemplateTitleMapNow;
		if (newPriTemplateIds.isEmpty()) {
			priTemplateTitleMapNow = priTemplateTitleMap;
		} else {
			priTemplates = getTemplateList();
			priTemplateTitleMapNow = priTemplates.stream()
					.collect(Collectors.toMap(PriTemplate::getTitle, UnaryOperator.identity(), (a, b) -> a));
		}

		return templates.parallelStream().filter(t -> tidRequestMap.containsKey(t.getTid())).map(t -> {
			// 到此处，一定可以获取个人模板，因为缺少的模板已经添加上了
			PriTemplate priTemplate = priTemplateTitleMapNow.get(t.getTitle());
			Template template = new Template(priTemplate, t);
			List<Keyword> keywords = getPubTemplateKeyWordsById(t.getTid());
			template.setKeywords(keywords);
			return template;
		}).collect(Collectors.toList());
	}

	/**
	 * 根据模板ID和标题查找本小程序账号下的公用模板列表<br>
	 * 注意1：受限于本小程序账号的类目，类目之外的公用模板无法查找到<br>
	 * 注意2：由于微信提供的接口不支持查询，此处采用遍历全部模板，此方法较重，不要频繁使用。
	 * 
	 * @param tids   模板ID集合
	 * @param titles 模板标题集合
	 * @return 公用模板列表
	 */
	private List<TemplateTitle> findMyPubTemplate(Set<Integer> tids, Set<String> titles) {
		if (tids.isEmpty()) {
			return Collections.emptyList();
		}
		return getMyPubTemplateTitleStream()
				.filter(template -> tids.contains(template.getTid()) || titles.contains(template.getTitle()))
				.collect(Collectors.toList());
	}

}
